class RaftNode {
  constructor(id, nodes = []) {
    // Server identity
    this.id = id;
    this.nodes = nodes;
    
    // Persistent state on all servers
    this.currentTerm = 0;
    this.votedFor = null;
    this.log = [];
    
    // Volatile state on all servers
    this.commitIndex = 0;
    this.lastApplied = 0;
    
    // Volatile state on leaders
    this.nextIndex = {};
    this.matchIndex = {};
    
    // Additional state for implementation
    this.state = 'follower';
    this.leaderId = null;
    this.votes = 0;
    
    // Timers
    this.electionTimeout = null;
    this.heartbeatInterval = null;
    
    // Initialize
    this.resetElectionTimeout();
  }
  
  resetElectionTimeout() {
    if (this.electionTimeout) {
      clearTimeout(this.electionTimeout);
    }
    
    // Random election timeout between 150-300ms
    const timeout = Math.floor(Math.random() * 150) + 150;
    this.electionTimeout = setTimeout(() => this.startElection(), timeout);
  }
  
  startElection() {
    if (this.state === 'leader') return;
    
    // Become candidate
    this.state = 'candidate';
    this.currentTerm += 1;
    this.votedFor = this.id;
    this.votes = 1; // Vote for self
    
    console.log(`Node ${this.id} starting election for term ${this.currentTerm}`);
    
    // Request votes from all other nodes
    this.nodes.forEach(node => {
      if (node.id !== this.id) {
        const voteGranted = this.requestVote(node);
        if (voteGranted) {
          this.votes += 1;
          
          // Check if we have majority
          if (this.votes > this.nodes.length / 2) {
            this.becomeLeader();
          }
        }
      }
    });
    
    // Reset election timeout
    this.resetElectionTimeout();
  }
  
  requestVote(node) {
    // In a real implementation, this would be an RPC
    const term = this.currentTerm;
    const candidateId = this.id;
    const lastLogIndex = this.log.length - 1;
    const lastLogTerm = lastLogIndex >= 0 ? this.log[lastLogIndex].term : 0;
    
    return node.handleVoteRequest(term, candidateId, lastLogIndex, lastLogTerm);
  }
  
  handleVoteRequest(term, candidateId, lastLogIndex, lastLogTerm) {
    // If the term is outdated, reject
    if (term < this.currentTerm) {
      return false;
    }
    
    // If we see a higher term, update our term and become follower
    if (term > this.currentTerm) {
      this.currentTerm = term;
      this.state = 'follower';
      this.votedFor = null;
    }
    
    // Check if we can vote for this candidate
    const canVote = (this.votedFor === null || this.votedFor === candidateId) && 
                    this.isLogUpToDate(lastLogIndex, lastLogTerm);
    
    if (canVote) {
      this.votedFor = candidateId;
      this.resetElectionTimeout();
      return true;
    }
    
    return false;
  }
  
  isLogUpToDate(lastLogIndex, lastLogTerm) {
    const myLastLogIndex = this.log.length - 1;
    const myLastLogTerm = myLastLogIndex >= 0 ? this.log[myLastLogIndex].term : 0;
    
    if (lastLogTerm > myLastLogTerm) return true;
    if (lastLogTerm === myLastLogTerm && lastLogIndex >= myLastLogIndex) return true;
    return false;
  }
  
  becomeLeader() {
    if (this.state === 'candidate' && this.votes > this.nodes.length / 2) {
      console.log(`Node ${this.id} became leader for term ${this.currentTerm}`);
      this.state = 'leader';
      this.leaderId = this.id;
      
      // Initialize leader state
      this.nodes.forEach(node => {
        if (node.id !== this.id) {
          this.nextIndex[node.id] = this.log.length;
          this.matchIndex[node.id] = 0;
        }
      });
      
      // Clear election timeout and start sending heartbeats
      clearTimeout(this.electionTimeout);
      this.electionTimeout = null;
      
      this.sendHeartbeats();
      this.heartbeatInterval = setInterval(() => this.sendHeartbeats(), 50);
    }
  }
  
  sendHeartbeats() {
    this.nodes.forEach(node => {
      if (node.id !== this.id) {
        this.appendEntries(node);
      }
    });
  }
  
  appendEntries(node) {
    // In a real implementation, this would be an RPC
    const term = this.currentTerm;
    const leaderId = this.id;
    const prevLogIndex = this.nextIndex[node.id] - 1;
    const prevLogTerm = prevLogIndex >= 0 ? this.log[prevLogIndex].term : 0;
    const entries = this.log.slice(this.nextIndex[node.id]);
    const leaderCommit = this.commitIndex;
    
    return node.handleAppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries, leaderCommit);
  }
  
  handleAppendEntries(term, leaderId, prevLogIndex, prevLogTerm, entries, leaderCommit) {
    // If the term is outdated, reject
    if (term < this.currentTerm) {
      return false;
    }
    
    // Reset election timeout as we heard from the leader
    this.resetElectionTimeout();
    
    // If we see a higher term, update our term
    if (term > this.currentTerm) {
      this.currentTerm = term;
      this.state = 'follower';
      this.votedFor = null;
    }
    
    // Update leader ID
    this.leaderId = leaderId;
    
    // Check if log contains an entry at prevLogIndex with term prevLogTerm
    if (prevLogIndex >= 0 && 
        (prevLogIndex >= this.log.length || 
         this.log[prevLogIndex].term !== prevLogTerm)) {
      return false;
    }
    
    // If an existing entry conflicts with a new one, delete it and all that follow
    let newEntries = [...entries];
    if (entries.length > 0) {
      let logIndex = prevLogIndex + 1;
      let entryIndex = 0;
      
      while (entryIndex < entries.length) {
        if (logIndex >= this.log.length || 
            this.log[logIndex].term !== entries[entryIndex].term) {
          // Delete all entries from this point
          this.log = this.log.slice(0, logIndex);
          // And append all remaining entries
          this.log = [...this.log, ...entries.slice(entryIndex)];
          break;
        }
        logIndex++;
        entryIndex++;
      }
    }
    
    // Update commit index if leader's is higher
    if (leaderCommit > this.commitIndex) {
      this.commitIndex = Math.min(leaderCommit, this.log.length - 1);
      this.applyCommittedEntries();
    }
    
    return true;
  }
  
  applyCommittedEntries() {
    // Apply log entries to state machine
    while (this.lastApplied < this.commitIndex) {
      this.lastApplied++;
      const entry = this.log[this.lastApplied];
      // In a real implementation, you would apply the command to your state machine
      console.log(`Node ${this.id} applying entry: ${JSON.stringify(entry)}`);
    }
  }
  
  // Client request to add a new entry
  clientRequest(command) {
    if (this.state !== 'leader') {
      console.log(`Node ${this.id} redirecting request to leader ${this.leaderId}`);
      return this.leaderId;
    }
    
    // Append entry to local log
    const entry = {
      term: this.currentTerm,
      command: command
    };
    this.log.push(entry);
    
    // Try to replicate to followers
    this.sendHeartbeats();
    
    return true;
  }
}

module.exports = RaftNode;